<template>
  <div id="d3">
    <div id="chinaMap" ref="chinaMap"></div>
  </div>
</template>

<script>
  import * as d3 from 'd3';
  import CHINAJSON from '../geo/china';
  import { lineData,circleData } from '../common/constants';
  export default {
    name: 'D3',
    data() {
      return {
        mapSvg: null,
        projectionFunc: null,
        hasRenderLinesData: [],
      }
    },
    mounted() {
      this.init();
    },
    methods: {
      init() { // 模块初始化
        this.renderMap();
      },
      // 线条高斯模糊特效
      renderLineFilter() {
        const defs= this.mapSvg.append('defs');
        defs.append('filter').attr('id', 'lineFilter').append('feGaussianBlur').attr('in','SourceGraphic').attr('stdDeviation','1');
      },
      renderMap() { // 渲染地图
        // svg画布绘制
        this.mapSvg = d3.select('#chinaMap').append('svg').attr('width', '100%').attr('height', '100%').style('background-color','#000');
        this.renderLineFilter();
        this.renderChinaPath();
        this.renderProvinceText();
        this.renderCircle();
        this.renderLines();
        this.renderArrow();
      },
      // 绘制中国地图
      renderChinaPath() {
        // 获取svg画布元素的rect
        const svgEl = this.$refs.chinaMap.querySelector('svg');
        const rect = svgEl.getBoundingClientRect();
        // 地图绘制在容器的上下左右正中间
        const projectionFunc = d3.geoMercator().fitSize([rect.width,rect.height], CHINAJSON);
        this.projectionFunc = projectionFunc;
        // 创建一个新的地理路径生成器
        const cityPath = d3.geoPath().projection(projectionFunc);
        // 创建包裹城市地理路径
        const city = this.mapSvg.append('g').attr('class','cities');
        // 绘制城市地理路径
        const cities = city
          .selectAll('.city')
          .data(CHINAJSON.features)
          .enter()
          .append('g')
          .attr('class', 'city')
          .attr('cursor', 'pointer')
          .attr('data-adcode', d => {
            return d.properties.adcode;
          })
          .on('mouseover', d => {
          })
          .on('mouseout', d => {
          });
        // 绘制城市路径
        cities.append('path')
          .attr('d', cityPath)
          .attr('stroke', '#1950CC')
          .attr('stroke-width', 1)
          .attr('fill', d => {
            return '#090A36';
          })
          .on('click', (d,i) => {
            console.log('====',i);
          })
      },
      // 绘制省份名称
      renderProvinceText() { // 绘制各省份名称
        const { projectionFunc } = this;
        // 绘制包裹各省份名称
        const province = this.mapSvg.append('g').attr('class','provincesText');
        // 开始绘制各省份text
        const provinces = province
          .selectAll('.provinceText')
          .data(CHINAJSON.features)
          .enter()
          .append('g')
          .attr('class', 'provinceText')
          .attr('cursor', 'pointer')
          .attr('data-adcode',d => {
            return d.properties.adcode;
          });

        provinces.append('text')
          .attr('x', d => {
            if (d.properties.center && d.properties.centroid) {
              return projectionFunc(d.properties.centroid)[0];
            }
          })
          .attr('y', d => {
            if (d.properties.center && d.properties.centroid) {
              return projectionFunc(d.properties.centroid)[1];
            }
          })
          .text(d => {
            return d.properties.name;
          })
          .style('font-size', 12)
          .style('fill', '#fff')
          .on('click',(d, i) => {
            console.log('d.properties.name===',d.properties.name);
          });
      },
      // 绘制标记中心园
      renderCircle() {
        const { projectionFunc } = this;
        const linesContainer = this.mapSvg.append('g').attr('class','lines');

        linesContainer
          .selectAll('.lines')
          .data(circleData)
          .enter()
          .append('circle')
          .attr('id',(d, i) => {
            return `circle${i}`;
          })
          .attr('cx',d => {
            return projectionFunc(d)[0];
          })
          .attr('cy',d => {
            return projectionFunc(d)[1];
          })
          .attr('r', '3')
          .attr('fill', '#fff');

        linesContainer
          .selectAll('.lines')
          .data(circleData)
          .enter()
          .append('circle')
          .attr('class', 'circleFlashing')
          .attr('cx', d => {
            return projectionFunc(d)[0];
          })
          .attr('cy', d => {
            return projectionFunc(d)[1];
          })
          .attr('r', '12')
          .attr('fill-opacity', '0')
          .attr('stroke-width', '1')
          .attr('stroke', '#fff');

        linesContainer
          .selectAll('.lines')
          .data(circleData)
          .enter()
          .append('circle')
          .attr('class', 'circleFlashing')
          .attr('cx', d => {
            return projectionFunc(d)[0];
          })
          .attr('cy', d => {
            return projectionFunc(d)[1];
          })
          .attr('r', '6')
          .attr('fill-opacity', '0')
          .attr('stroke', '#fff');
      },
      // 绘制线条流向
      renderLines() {
        const { projectionFunc } = this;

        const transPath = obj => {
          return `M${obj.starPos[0]},${obj.starPos[1]} Q${
            obj.controlPos[0]
          },${obj.controlPos[1]} ${obj.endPos[0]},${obj.endPos[1]}`;
        };

        const baseLineGroups = this.mapSvg.append('g').attr('class', 'baseLineGroups');
        baseLineGroups
          .selectAll('.baseLineGroups')
          .data(lineData)
          .enter()
          .append('path')
          .attr('stroke', d => {
            return d.color;
          })
          .attr('stroke-width',d => {
            return d.lineWidth;
          })
          .attr('fill', 'none')
          .attr('d', (d) => {
            const starPos = projectionFunc(d.from);
            const endPos = this.projectionFunc(d.to);
            const plusOrMinus = this.isBezierCtrPoint(d.from, d.to); // 决定加或者减
            const controlPos = this.computeControlPoint(
              starPos,
              endPos,
              plusOrMinus
            );
            return transPath({ starPos,endPos,controlPos });
          })
          .attr('marker-end', (d,i) => {
            return `url(#arrow${i})`;
          })
          .attr('cursor', 'pointer')
          .on('mouseover', function(d, i) {
          })
          .on('mouseout', function() {
          })
          .on('click', () => {
          });
      },
      // 绘制箭头
      renderArrow() {
        // 箭头标记
        const defs = this.mapSvg.append('defs').attr('class', 'baseLineArrow');

        // 静态箭头
        defs
          .selectAll('.baseLineArrow')
          .data(lineData)
          .enter()
          .append('marker')
          .attr('id', (d,i) => {
            return `arrow${i}`
          })
          .attr('markerUnits', 'strokeWidth')
          .attr('markerWidth', '7')
          .attr('markerHeight', '7')
          .attr('viewBox', '0 0 12 12')
          .attr('refX', '6')
          .attr('refY', '6')
          .attr('orient', 'auto')
          .append('path')
          .attr('d', 'M2,2 L2,11 L10,6 L2,2')
          .attr('fill', d => {
            return d.color;
          });

        // 动态箭头
        defs
          .selectAll('.baseLineArrow')
          .data(lineData)
          .enter()
          .append('marker')
          .attr('id', (d,i) => {
            return `m-arrow${i}`;
          })
          .attr('markerUnits', 'strokeWidth')
          .attr('markerWidth', '7')
          .attr('markerHeight', '7')
          .attr('viewBox', '0 0 12 12')
          .attr('refX', '6')
          .attr('refY', '6')
          .attr('orient', 'auto')
          .append('path')
          .attr('d', 'M2,2 L2,11 L10,6 L2,2')
          .attr('fill', d => {
            return d.color;
          });
      },
      // 判断两点之间是否已经设置过二次贝塞尔曲线控制点,如果设置过,控制点取另一个点
      isBezierCtrPoint(from, to) {
        if (!this.hasRenderLinesData.length) {
          return false;
        }

        return this.hasRenderLinesData.some((v, idx) => {
          return (
            v.from[0] === to[0] &&
            v.from[1] === to[1] &&
            v.to[0] === from[0] &&
            v.to[1] === from[1]
          );
        });
      },
      // 计算二次贝赛尔曲线控制点
      computeControlPoint(ps, pe, plusOrMinus) {
        let deltaX = null;
        let deltaY = null;
        if (plusOrMinus) {
          deltaX =
            (ps[0] + pe[0]) / 2 +
            100 /
            Math.sqrt(
              Math.pow((pe[0] - ps[0]) / (pe[1] - ps[1]), 2) + 1
            );
          deltaY =
            (ps[1] + pe[1]) / 2 +
            100 /
            Math.sqrt(
              Math.pow((pe[1] - ps[1]) / (pe[0] - ps[0]), 2) + 1
            );
        } else {
          deltaX =
            (ps[0] + pe[0]) / 2 -
            100 /
            Math.sqrt(
              Math.pow((pe[0] - ps[0]) / (pe[1] - ps[1]), 2) + 1
            );
          deltaY =
            (ps[1] + pe[1]) / 2 -
            100 /
            Math.sqrt(
              Math.pow((pe[1] - ps[1]) / (pe[0] - ps[0]), 2) + 1
            );
        }
        return [deltaX, deltaY];
      },
    }
  }
</script>

<style scoped lang="scss">
@import "../assets/style/main.css";
#d3{
  height: calc(100vh - 100px);
  #chinaMap {
    height: 100%;
    width: 100%;
    background: transparent;
  }
}
</style>
